package thesis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;


import com.aliasi.util.Pair;

import experiment.Dimension;

public class DataUtil {
	public static double getInfoWeight(int size) {
		return Math.pow(2, size);
	}

	private static double combo(int n, int m) {
		double total = 1;
		for (int i = 1; i <= m; i++) {
			total *= (n - i + 1) / i;
		}
		return total;
	}

	public static int map2discrete(double prob){
		int num = 9;
		if(prob>=0.9){
			return num;
		}
		return (int) Math.floor(prob*num);		
	}
	public static double getTotalInfo(int size) {
		double total = 0;

		int limit = FSModule.maxInfoSize == 0 ? size : FSModule.maxInfoSize;

		for (int i = 1; i <= limit; i++) {
			total += combo(size, i) * getInfoWeight(i);
		}
		return total;
	}

	public static double cosDistance(ArrayList<Double> v1, ArrayList<Double> v2) {
		double total = 0, totalV1 = 0, totalV2 = 0;
		final double eps = 1E-6;
		for (int i = 0; i < v1.size(); i++) {
			total += v1.get(i) * v2.get(i);
			totalV1 += v1.get(i) * v1.get(i);
			totalV2 += v2.get(i) * v2.get(i);
		}
		double tmp = Math.sqrt(totalV1 * totalV2);
		if (tmp < eps) {
			return 1;
		}
		return 1 - total / tmp;
	}

	public static double[][] marginal2Joint(int dimensionLimit,
			final List<Dimension> dimensions,
			final HashMap<Long, DataObject> memoryTweets,
			ArrayList<InfoUnit> units) {
		List<ArrayList<Double>> probs = new ArrayList<ArrayList<Double>>();
		for (int i = 0; i < memoryTweets.size(); i++) {
			probs.add(new ArrayList<Double>());
		}
		// membership is used in recursive function
		boolean[] membership = new boolean[dimensions.size()];
		// recursively generate joint concepts
		generateJointConcepts(dimensionLimit, memoryTweets, dimensions, units,
				probs, membership, 0);
		double[][] finalProbs = null;
		if (!probs.isEmpty()) {
			finalProbs = new double[probs.size()][probs.get(0).size()];
			for (int i = 0; i < probs.size(); i++) {
				for (int j = 0; j < probs.get(i).size(); j++) {
					finalProbs[i][j] = probs.get(i).get(j);
				}
			}
		}
		return finalProbs;
	}

	private static void generateJointConcepts(int dimensionLimit,
			final HashMap<Long, DataObject> memoryTweets,
			final List<Dimension> dimensions,
			final ArrayList<InfoUnit> units, List<ArrayList<Double>> probs,
			boolean[] membership, int index) {
		if (index == dimensions.size()) {
			jointConcept2InfoUnits(dimensionLimit, memoryTweets, dimensions,
					units, probs, membership);
		} else {
			membership[index] = true;
			generateJointConcepts(dimensionLimit, memoryTweets, dimensions,
					units, probs, membership, index + 1);
			membership[index] = false;
			generateJointConcepts(dimensionLimit, memoryTweets, dimensions,
					units, probs, membership, index + 1);
		}
	}

	private static void jointConcept2InfoUnits(int dimensionLimit,
			final HashMap<Long, DataObject> memoryTweets,
			final List<Dimension> dimensions,
			final ArrayList<InfoUnit> units, List<ArrayList<Double>> probs,
			boolean[] membership) {
		ArrayList<ArrayList<Pair<String, HashMap<Long, Double>>>> jointConcepts = new ArrayList<ArrayList<Pair<String, HashMap<Long, Double>>>>();
		ArrayList<Integer> conceptIds = new ArrayList<Integer>();
		for (int i = 0; i < membership.length; i++) {
			if (membership[i]) {
				ArrayList<Pair<String, HashMap<Long, Double>>> dimValues = new ArrayList<Pair<String, HashMap<Long, Double>>>();
				for (String key : dimensions.get(i).value2obj.keySet()) {
					dimValues.add(new Pair<String, HashMap<Long, Double>>(key,
							dimensions.get(i).value2obj.get(key)));
				}
				jointConcepts.add(dimValues);
				conceptIds.add(i);
			}
		}
		if (!jointConcepts.isEmpty() && jointConcepts.size() <= dimensionLimit) {
			int[] values = new int[jointConcepts.size()];
			generateInfoUnits(memoryTweets, jointConcepts, conceptIds, units,
					probs, values, 0);
		}

	}

	private static void generateInfoUnits(
			final HashMap<Long, DataObject> memoryTweets,
			final ArrayList<ArrayList<Pair<String, HashMap<Long, Double>>>> jointConcepts,
			final ArrayList<Integer> conceptIds,
			final ArrayList<InfoUnit> units, List<ArrayList<Double>> probs,
			int[] values, int index) {
		if (index == jointConcepts.size()) {
			// base case
			// add a new InfoUnit
			InfoUnit unit = new InfoUnit(units.size(), jointConcepts.size());
			units.add(unit);
			// add a new column in probs
			for (ArrayList<Double> rels : probs) {
				rels.add(0.0);
			}
			// assign prob values to the new column
			List<HashMap<Long, Double>> relatedTweets = new ArrayList<HashMap<Long, Double>>();
			String name = "";
			for (int i = 0; i < values.length; i++) {
				relatedTweets.add(jointConcepts.get(i).get(values[i]).b());
				name += jointConcepts.get(i).get(values[i]).a() + " ";
				unit.addDimValue(conceptIds.get(i), values[i]);
			}
			unit.setName(name);

			for (DataObject t : memoryTweets.values()) {
				double prob = 1;
				// joint prob is product of marginal probabilities
				for (HashMap<Long, Double> rels : relatedTweets) {
					Double marginalProb = rels.get(t.getDbId());
					if (marginalProb == null) {
						prob = 0;
						break;
					}
					prob *= marginalProb;
				}
				int internId = t.getInternId();
				if (prob != 0) {
					probs.get(internId).set(units.size() - 1, prob);
				}
			}
			// check whether to keep this unit
			boolean keep = false;
			for (int i = 0; i < probs.size(); i++) {
				if (probs.get(i).get(units.size() - 1) >= FSModule.relThresh) {
					keep = true;
					break;
				}
			}
			if (!keep) {
				for (int i = 0; i < probs.size(); i++) {
					probs.get(i).remove(units.size() - 1);
				}
				units.remove(units.size() - 1);
			}

		} else {
			for (int i = 0; i < jointConcepts.get(index).size(); i++) {
				values[index] = i;
				generateInfoUnits(memoryTweets, jointConcepts, conceptIds,
						units, probs, values, index + 1);
			}
		}

	}

	public static double dist(DataObject t1, DataObject t2) {
		// return 1;
		return t1.dist(t2);
	}

}
